/***************************************************************************
 * Copyright 2012-2013 TXT e-solutions SpA
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * This work was performed within the IoT@Work Project
 * and partially funded by the contract ICT-257367.
 *
 * Authors:
 *      Salvatore Piccione (TXT e-solutions SpA)
 *
 * Contributors:
 *        Domenico Rotondi (TXT e-solutions SpA)
 **************************************************************************/
package it.txt.ens.client.subscriber.printer.osgi;

import it.txt.ens.client.subscriber.ENSSubscriber;
import it.txt.ens.client.subscriber.osgi.RegisteredServices;
import it.txt.ens.client.subscriber.printer.ENSEventPrinterImpl;
import it.txt.ens.core.ENSResource;
import it.txt.ens.core.factory.ENSResourceFactory;
import it.txt.ens.core.factory.URIParseException;
import it.txt.ens.core.util.converter.MapConverter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;

/**
 * @author Salvatore Piccione (TXT e-solutions SpA - salvatore.piccione AT txtgroup.com)
 * @author Domenico Rotondi (TXT e-solutions SpA - domenico.rotondi AT txtgroup.com)
 *
 */
public class Activator implements BundleActivator {
    
    private static final ResourceBundle LOGGING_MESSAGES = ResourceBundle.getBundle(
            "logging-messages/" + Activator.class.getSimpleName(), Locale.ROOT);
    private static final Logger LOGGER = Logger.getLogger(Activator.class.getName());
    private static final String DEFAULT_CONFIG_DIR = "conf/";
    public static final String PROPERTY_NAME_FORMAT = "ENSSubscriber-PrinterApp-%1$s.properties";
    private static final String SUBSCRIBER_IMPLEMENTATION_ID = "default";
    private static final int CONFIGURATION_CAPACITY = 100;
//    private static final String SUBSCRIBER_SEARCH_FILTER_FORMAT = 
//            "(&(" + it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY
//            + "=%1$s)(" + it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY + "=%2$d)(" +
//            ENSResourceFactory.PATTERN + "=%3$s))";
//    private final BlockingQueue<Filter> filters;
    private final BlockingQueue<SubscriberFilter> filters;
    private ConfiguredSubscriberTracker tracker;
    
    public Activator () {
//        filters = new LinkedBlockingQueue<Filter>(CONFIGURATION_CAPACITY);
        filters = new LinkedBlockingQueue<SubscriberFilter>(CONFIGURATION_CAPACITY);
    }
    
    /* (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start(final BundleContext context) throws Exception {
        if (LOGGER.isLoggable(Level.INFO)) {
            LOGGER.log(Level.INFO, "[ENS Subscriber - Event printer example] Bundle ID: " + 
                context.getBundle().getBundleId());
        }
        
        //retrieve the configuration admin service
        ServiceReference<ConfigurationAdmin> configAdminSR = context.getServiceReference(ConfigurationAdmin.class);
        if (configAdminSR == null)
            throw new Exception("No " + ConfigurationAdmin.class.getName() + " available");
        ConfigurationAdmin configAdmin = context.getService(configAdminSR);
        
        File currentPropertiesFile;
        
        Properties currentProperties;
        Configuration config;
        FileInputStream stream = null;
        Dictionary<String,Object> configuration;
        int i = 1;
        while ((currentPropertiesFile = new File(DEFAULT_CONFIG_DIR + String.format(PROPERTY_NAME_FORMAT, i++))).exists()) {
            currentProperties = new Properties();
            try {
                stream = new FileInputStream(currentPropertiesFile);
                currentProperties.load(stream);

                config = configAdmin.createFactoryConfiguration(RegisteredServices.SUBSCRIBER_MANAGED_SERVICE_FACTORY_PID, null);
            } catch (IOException e) {
                LOGGER.log(Level.WARNING, MessageFormat.format(
                        LOGGING_MESSAGES.getString("errorWhileReadingPropertiesFile"), 
                        currentPropertiesFile.getAbsolutePath()), e);
                continue;
            } finally {
                if (stream != null)
                    try {
                        stream.close();
                    } catch (Exception e) {}
            }

            configuration = MapConverter.convert(currentProperties);
            configuration.put(it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY,
                    (Long)context.getBundle().getBundleId());
            configuration.put(it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY,
                    SUBSCRIBER_IMPLEMENTATION_ID);
            config.update(configuration);
            
            Object patternOBJ = configuration.get(ENSResourceFactory.PATTERN);
            String pattern;
            String namespace;
            if (patternOBJ == null) {
                Object resourceOBJ = configuration.get(ENSResourceFactory.URI);
                if (resourceOBJ == null)
                    throw new ConfigurationException(ENSResourceFactory.URI, "Missing property");
                else {
                    ServiceReference<ENSResourceFactory> resourceFactorySR = context.getServiceReference(
                        ENSResourceFactory.class);
                    ENSResourceFactory resourceFactory = context.getService(resourceFactorySR);
                    ENSResource resource;
                    try {
                        resource = resourceFactory.create(new URI(resourceOBJ.toString()));
                        pattern = resource.getPattern();
                        namespace = resource.getNamespace();
                    } catch (IllegalArgumentException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    } catch (URIParseException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    } catch (URISyntaxException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    }
                    context.ungetService(resourceFactorySR);
                }
            } else {
                pattern = (String) patternOBJ;
                namespace = (String) configuration.get(ENSResourceFactory.NAMESPACE);
            }
            
            //this doesn't work because the escape doesn't work
//            EqualsFilter implementationFilter = new EqualsFilter(
//                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY, 
//                    LdapEncoder.filterEncode(SUBSCRIBER_IMPLEMENTATION_ID));
//            EqualsFilter ownershipFilter = new EqualsFilter(
//                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY,
//                    (int) context.getBundle().getBundleId());
//            EqualsFilter patternFilter = new EqualsFilter(ENSResourceFactory.PATTERN,LdapEncoder.filterEncode(pattern));
//            AndFilter filter = new AndFilter().and(implementationFilter).and(ownershipFilter).and(patternFilter);
//            Filter osgiFilter = FrameworkUtil.createFilter(filter.toString());
            
            try {
                filters.put(new SubscriberFilter(SUBSCRIBER_IMPLEMENTATION_ID,context.getBundle().getBundleId(),namespace,pattern));
//            } catch (InvalidSyntaxException e) {
//                throw new ConfigurationException(null, "An error occurred while tracking subscription services using filter " + 
//                        osgiFilter.toString(), e);
            } catch (InterruptedException e) {
                continue;
            }
        }
        tracker = new ConfiguredSubscriberTracker(context);
        tracker.start();
    }

    /* (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop(BundleContext context) throws Exception {
        tracker.shutdown(context);
        tracker.join();
        tracker = null;
    }
    
    private class ConfiguredSubscriberTracker extends Thread {
        private final Logger TRACKER_LOGGER = Logger.getLogger(ConfiguredSubscriberTracker.class.getName());
        private static final long SUBSCRIBER_LOOKUP_TIMEOUT = 10000;
        private static final int TIMEOUT = 100;
//        private ServiceTracker<ENSSubscriber,ENSSubscriber> subscriberTracker;
        private boolean run;
        private volatile BundleContext context;
        private final HashMap<String, ENSEventPrinterImpl> serviceMap;
        private List<ServiceReference<ENSSubscriber>> subscriberReferences;
        
        public ConfiguredSubscriberTracker (BundleContext context) throws ConfigurationException {
            run = true;
            this.context =context;
            serviceMap = new HashMap<String, ENSEventPrinterImpl>();
            subscriberReferences = Collections.synchronizedList(new LinkedList<ServiceReference<ENSSubscriber>> ());
        }
        
        public void run() {
//            Filter filter = null;
            SubscriberFilter filter = null;
            String id;
            while (run) {
                try {
                    filter = filters.poll(TIMEOUT, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    continue;
                }
                if (filter != null) {
                    //cannot use the service tracker because the special characters escape doesn't work!
//                    subscriberTracker = new ServiceTracker<ENSSubscriber, ENSSubscriber>(context, filter, null);
//                    subscriberTracker.open();
                    ENSSubscriber subscriber = null;
                    Collection<ServiceReference<ENSSubscriber>> subscriberReferences;
                    long startTime = System.currentTimeMillis();
                    try {
                        do{
                            subscriberReferences = context.getServiceReferences(ENSSubscriber.class, filter.toString());
                            if (!subscriberReferences.isEmpty())
                                subscriber = selectSubscriber(subscriberReferences, filter);
                        } while (subscriber == null && (System.currentTimeMillis() - startTime) <= SUBSCRIBER_LOOKUP_TIMEOUT);                  
//                        subscriber = subscriberTracker.waitForService(SUBSCRIBER_LOOKUP_TIMEOUT);
                        
//                    } catch (InterruptedException e) {
//                        TRACKER_LOGGER.log(Level.SEVERE, "The service tracker has been unexpectly stopped.", e);
                    } catch (InvalidSyntaxException e) {
                        TRACKER_LOGGER.log(Level.SEVERE, "The service tracker has been unexpectly stopped.", e);
                        continue;
                    }
//                    subscriberTracker.close();
                    if (subscriber == null) {
                        TRACKER_LOGGER.log(Level.SEVERE, "No subscriber found with selection criteria " + filter.toString());
                        return;
                    }
                    subscriber = context.getService(subscriberReferences.iterator().next());
                    id = subscriber.getTargetResource().getNamespace() + "@" + subscriber.getTargetResource().getPattern();
                    ENSEventPrinterImpl eventPrinter = new ENSEventPrinterImpl(id, subscriber);
                    //TODO register printers in the OSGi service registry
                    serviceMap.put(id, eventPrinter);
                    try {
                        eventPrinter.init();
                        if (eventPrinter.isInitialised()) {
                            eventPrinter.start();
                            if (LOGGER.isLoggable(Level.FINE))
                                LOGGER.log(Level.FINE, MessageFormat.format(LOGGING_MESSAGES.getString("eventPrinterStarted"),id));
                        }
                    } catch (Exception e) {
                        LOGGER.log(Level.SEVERE, MessageFormat.format( 
                                LOGGING_MESSAGES.getString("errorWhileStartingEventPrinter"),id),e);
                        continue;
                    }
    //
//                if (properties != null) {
//                    StringBuilder builder = new StringBuilder();
//                    boolean isFirst = true;
//                    java.util.Enumeration<String> keys = properties.keys();
//                    String key;
//                    while (keys.hasMoreElements()) {
//                        key = keys.nextElement();
//                        if (isFirst)
//                            isFirst = false;
//                        else
//                            builder.append("\n");
//                        builder.append(key);
//                        builder.append(": ");
//                        builder.append(properties.get(key));
//                    }
//                    LOGGER.log(Level.FINER, "Registering an instance of " + ENSEventPrinter.class.getName() + 
//                            " with pid " + pid + "and the following properties:\n" + builder.toString());
//                }
                
                }
            }
            
        }
        
        private ENSSubscriber selectSubscriber (Collection<ServiceReference<ENSSubscriber>> subscriberReferences, SubscriberFilter filter) {
            Iterator<ServiceReference<ENSSubscriber>> iterator=  subscriberReferences.iterator();
            ENSSubscriber subscriber = null;
            ServiceReference<ENSSubscriber> subscriberSR;
            while (iterator.hasNext() && subscriber == null) {
                subscriberSR = iterator.next();
                if (filter.match(subscriberSR)) {
                    subscriber = context.getService(subscriberSR);
                    this.subscriberReferences.add(subscriberSR);
                    if (LOGGER.isLoggable(Level.FINER))
                        LOGGER.log(Level.FINER, MessageFormat.format(LOGGING_MESSAGES.getString("addedSubscriberReference"),
                                subscriberSR.getProperty(Constants.SERVICE_PID)));
                }
            }
            return subscriber;
        }
        
        public void shutdown(BundleContext context) {
            run = false;
            this.context = context;
            ENSEventPrinterImpl eventPrinter;
            for (Entry<String, ENSEventPrinterImpl> entry : serviceMap.entrySet()) {
                eventPrinter = entry.getValue();
                try {
                    if (eventPrinter.isWorking()) {
                        eventPrinter.stop();
                        eventPrinter.shutdown();
                    }
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, MessageFormat.format(LOGGING_MESSAGES.getString("eventPrinterStoppingError"), eventPrinter.getID()), e);
                }
                LOGGER.log(Level.INFO, MessageFormat.format(LOGGING_MESSAGES.getString("eventPrinterStopped"), entry.getKey()));
            }
            ServiceReference<ConfigurationAdmin> configAdminSR = context.getServiceReference(ConfigurationAdmin.class);
            ConfigurationAdmin configAdmin = context.getService(configAdminSR);
            String pid;
            Configuration config;
            synchronized (subscriberReferences) {
                for (ServiceReference<ENSSubscriber> reference : subscriberReferences) {
                    pid = (String) reference.getProperty(Constants.SERVICE_PID);
                    if (LOGGER.isLoggable(Level.FINER))
                        LOGGER.log(Level.FINER, MessageFormat.format(LOGGING_MESSAGES.getString("deletingSubscriberReference"),pid));
                    try {
                        if (pid != null) {
                            config= configAdmin.getConfiguration(pid);
                            if (config != null)
                                config.delete();
                        }
                    } catch (IOException e) {
                        if (LOGGER.isLoggable(Level.WARNING));
                            LOGGER.log(Level.WARNING, MessageFormat.format(LOGGING_MESSAGES.getString("configurationDeletionError"), pid), e);
                    }
                    context.ungetService(reference);
                }
            }
            context.ungetService(configAdminSR);
        }
    }
    
    private static class SubscriberFilter implements Filter {

        private String unescapedImplementationID;
        private String unescapedPattern;
        private String unescapedNamespace;
        private long bundleOwnerID;
        private String encodedOsgiFilter;
        
        public SubscriberFilter (String unescapedImplementationID, long bundleOwnerID, 
                String unescapedNamespace, String unescapedPattern) {
            if (unescapedImplementationID == null)
                throw new IllegalArgumentException ("The implementation ID cannot be null");
            if (unescapedNamespace == null)
                throw new IllegalArgumentException ("The namespace ID cannot be null");
            if (unescapedPattern == null)
                throw new IllegalArgumentException ("The pattern cannot be null");
            this.unescapedImplementationID = unescapedImplementationID;
            this.unescapedPattern = unescapedPattern;
            this.unescapedNamespace = unescapedNamespace;
            this.bundleOwnerID = bundleOwnerID;
            
            EqualsFilter implementationFilter = new EqualsFilter(
                  it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY, 
                  unescapedImplementationID);
            EqualsFilter ownershipFilter = new EqualsFilter(
                  it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY, bundleOwnerID + "");
            AndFilter filter = new AndFilter().and(implementationFilter).and(ownershipFilter);
            encodedOsgiFilter = filter.encode();
        }

        /* (non-Javadoc)
         * @see org.osgi.framework.Filter#match(org.osgi.framework.ServiceReference)
         */
        @Override
        public boolean match(ServiceReference<?> reference) {
            return matches(reference.getProperty(
                it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY),
                reference.getProperty(ENSResourceFactory.NAMESPACE),
                reference.getProperty(ENSResourceFactory.PATTERN),
                (Long) reference.getProperty(
                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY));
        }

        /* (non-Javadoc)
         * @see org.osgi.framework.Filter#match(java.util.Dictionary)
         */
        @Override
        public boolean match(Dictionary<String, ?> dictionary) {
            return matches(dictionary.get(
                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY),
                    dictionary.get(ENSResourceFactory.NAMESPACE),
                    dictionary.get(ENSResourceFactory.PATTERN),
                    (Long) dictionary.get(it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY));
        }

        /* (non-Javadoc)
         * @see org.osgi.framework.Filter#matchCase(java.util.Dictionary)
         */
        @Override
        public boolean matchCase(Dictionary<String, ?> dictionary) {
            return matches(dictionary.get(
                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY),
                    dictionary.get(ENSResourceFactory.NAMESPACE),
                    dictionary.get(ENSResourceFactory.PATTERN),
                    (Long) dictionary.get(it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY));
        }

        /* (non-Javadoc)
         * @see org.osgi.framework.Filter#matches(java.util.Map)
         */
        @Override
        public boolean matches(Map<String, ?> map) {
            return matches(map.get(
                    it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_IMPLEMENTATION_KEY),
                    map.get(ENSResourceFactory.NAMESPACE),
                    map.get(ENSResourceFactory.PATTERN),
                    (Long) map.get(it.txt.ens.client.subscriber.osgi.FilterAttributes.SUBSCRIBER_OWNER_KEY));
        }
        
        private boolean matches (Object thatImplementationID, Object thatNamespace, Object thatPattern, long ownerID) {
            System.out.println(thatImplementationID + "-----" + thatNamespace + "-----" + thatPattern + "-----" + ownerID);
            
            return  unescapedImplementationID.equals(thatImplementationID) &&
                    unescapedNamespace.equals(thatNamespace) &&
                    unescapedPattern.equals(thatPattern) &&
                    bundleOwnerID == ownerID;
        }

        
        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null)
                return false;
            if (obj instanceof SubscriberFilter) {
                SubscriberFilter that = (SubscriberFilter) obj;
                return this.bundleOwnerID == that.bundleOwnerID && 
                    this.unescapedImplementationID.equals(that.unescapedImplementationID) &&
                    this.unescapedNamespace.equals(that.unescapedNamespace) &&
                    this.unescapedPattern.equals(that.unescapedPattern);
            }
            return false;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return encodedOsgiFilter.hashCode();
        }

        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            System.out.println("Subscriuber filter = " + encodedOsgiFilter);
            
            return encodedOsgiFilter;
        }

        /**
         * @return the unescapedImplementationID
         */
//        public String getUnescapedImplementationID() {
//            return unescapedImplementationID;
//        }

        /**
         * @param unescapedImplementationID the unescapedImplementationID to set
         */
//        public void setUnescapedImplementationID(String unescapedImplementationID) {
//            this.unescapedImplementationID = unescapedImplementationID;
//        }

        /**
         * @return the unescapedPattern
         */
//        public String getUnescapedPattern() {
//            return unescapedPattern;
//        }

        /**
         * @param unescapedPattern the unescapedPattern to set
         */
//        public void setUnescapedPattern(String unescapedPattern) {
//            this.unescapedPattern = unescapedPattern;
//        }

        /**
         * @return the unescapedNamespace
         */
//        public String getUnescapedNamespace() {
//            return unescapedNamespace;
//        }

        /**
         * @param unescapedNamespace the unescapedNamespace to set
         */
//        public void setUnescapedNamespace(String unescapedNamespace) {
//            this.unescapedNamespace = unescapedNamespace;
//        }

        /**
         * @return the bundleOwnerID
         */
//        public long getBundleOwnerID() {
//            return bundleOwnerID;
//        }

        /**
         * @param bundleOwnerID the bundleOwnerID to set
         */
//        public void setBundleOwnerID(long bundleOwnerID) {
//            this.bundleOwnerID = bundleOwnerID;
//        }
    }
}
